// X-Chat OTR (Off The Record) messaging by Konrad Meyer
//
// See `license.txt' for a copy of the license (GPL).

#define XC_COLOR "\003"
#define XC_COL(str) XC_COLOR str

#define _GNU_SOURCE
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#include <pwd.h>
#include "xchat-plugin.h"

#include <libotr/proto.h>
#include <libotr/privkey.h>
#include <libotr/message.h>

static xchat_plugin *ph;
static OtrlUserState userstate;
static OtrlMessageAppOps ui_cbs;

#include "req-structs.h"

struct passwd *pwptr;
char *username;
char *privkeyfile;
char *fingerpfile;
char *nickname = NULL;

// error function
static int xc_error(char *str){
  xchat_print(ph, str);
  return 0;
}

// us receiving
static int privmsg_rcv_cb(char *word[], char *word_eol[], void *userdata){
  OtrlTLV *tlvs = NULL;
  char *sender = strdupa((word[1] + 1)), *newmessage = NULL;
  const char *realmsg;
  int len = strlen(sender), i, ignore_message;
  xchat_context *ctx = xchat_find_context(ph, NULL, sender);
  if (ctx){
    if (nickname) free(nickname);
    nickname = strdup(ctx->me->nick);
  }

  for (i = 0; i < len; i++){
    if (sender[i] == '!'){
      sender[i] = '\0';
      break;
    }
  }

  // disregard channel messages
  if (*word[3] == '#') return XCHAT_EAT_NONE;

  ignore_message = otrl_message_receiving(userstate, &ui_cbs, NULL,
      username, "irc", sender, (word_eol[4] + 1), &newmessage, &tlvs,
      NULL, NULL);

  if (ignore_message){
    // internal protocol message
  }
  else{
    if (ctx){
      xchat_set_context(ph, ctx);
      ctx->msg_said = 1;
    }
    if (newmessage) realmsg = newmessage;
    else realmsg = word_eol[4] + 1;
    xchat_printf(ph, XC_COL("18") "<%s>\t"
        XC_COL("14") "%s", sender, realmsg);
    if (newmessage) otrl_message_free(newmessage);
  }
  if (tlvs) otrl_tlv_free(tlvs);

  return XCHAT_EAT_ALL;
}

// us sending
static int privmsg_send_cb(char *word[], char *word_eol[], void *userdata){
  gcry_error_t err;
  char *otr_message = NULL, *msg = strdupa(word_eol[1]);
  const char *realmsg;
  xchat_context *xcon = xchat_get_context(ph);
  const char *recipient = "";
  int i;

  if (xcon){
    if (nickname) free(nickname);
    nickname = strdup(xcon->me->nick);
    recipient = xcon->channel;
  }

  if (recipient[0] == '#') return XCHAT_EAT_NONE;
  if (recipient[0] == '\0'){
    xc_error("Could not find current context (Wtf?)");
    return XCHAT_EAT_NONE;
  }

  err = otrl_message_sending(userstate, &ui_cbs, NULL, username, "irc",
      recipient, msg, NULL, &otr_message, NULL, NULL);
  if (err){
    xchat_print(ph, "Oh no, we couldn't send message!");
  }
  else{
    if (otr_message) realmsg = otr_message;
    else realmsg = word_eol[1];
    xchat_printf(ph, XC_COL("31") "<%s>\t" XC_COL("30") "%s",
        nickname, realmsg);
    xchat_commandf(ph, "PRIVMSG %s :%s", recipient, realmsg);
    if (otr_message) otrl_message_free(otr_message);
  }

  return XCHAT_EAT_ALL;
}

// on /STARTOTR
static int startotr_cb(char *word[], char *word_eol[], void *userdata){
  xchat_context *xcon = xchat_get_context(ph);
  if (*xcon->channel == '#'){
    xc_error("Sorry, can't use STARTOTR in a channel.");
    return XCHAT_EAT_ALL;
  }
  return XCHAT_EAT_ALL;
}

// Callback Forrest {{{
static OtrlPolicy ui_policy(void *opdata, ConnContext *context){
  // we want to allow most stuff, but not V1 encryption.
  return OTRL_POLICY_DEFAULT ^ OTRL_POLICY_ALLOW_V1;
}
static void ui_create_privkey(void *opdata, const char *accountname, const char *protocol){
  xchat_command(ph, "GUI MSGBOX Generating_private_key...");
  otrl_privkey_generate(userstate, privkeyfile, username, "irc");
}
static int ui_is_logged_in(void *opdata, const char *accountname, const char *protocol, const char *recipient){
  return -1;
}
static void ui_inject_message(void *opdata, const char *accountname, const char *protocol, const char *recipient, const char *message){
  xchat_commandf(ph, "MSG %s %s", recipient, message);
}
static void ui_notify(void *opdata, OtrlNotifyLevel level, const char *accountname, const char *protocol, const char *username, const char *title, const char *primary, const char *secondary){
  // nfi
}
static int ui_display_otr_message(void *opdata, const char *accountname, const char *protocol, const char *username, const char *msg){
  xchat_print(ph, msg);
  return 0;
}
static const char *ui_protocol_name(void *opdata, const char *protocol){
  const char *irc = "IRC";
  const char *other = "Unknown";
  if (!strcmp("irc", protocol)) return irc;
  return other;
}
static void ui_new_fingerprint(void *opdata, OtrlUserState us, const char *accountname, const char *protocol, const char *username, unsigned char fingerprint[20]) {
  xchat_printf(ph, "New fingerprint received for %s on %s (%s).",
      accountname, protocol, username);
}
static void ui_gone_secure(void *opdata, ConnContext *context){
}
static void ui_gone_insecure(void *opdata, ConnContext *context){
}
static void ui_still_secure(void *opdata, ConnContext *context, int is_reply){
}
static void ui_log_message(void *opdata, const char *message){
}
static int ui_max_message_size(void *opdata, ConnContext *context){
  return 417; // IRC
}
// }}}

__declspec(dllexport)
int xchat_plugin_init(xchat_plugin *plugin_handle, char **plugin_name, char **plugin_desc, char **plugin_version, char **arg){
  struct stat statbuf;
  xchat_context *ctx;
  ph = plugin_handle;

  pwptr = getpwuid(getuid());
  if (!pwptr) return xc_error("Failed to load otr plugin, couldn't get "
      "username");
  username = strdup(pwptr->pw_name);
  privkeyfile = malloc(50 * sizeof (char));
  fingerpfile = malloc(50 * sizeof (char));
  if (!privkeyfile || !fingerpfile) return xc_error("Couldn't malloc "
      "space for privkeyfile/fingerpfile, loading OTR failed");
  snprintf(privkeyfile, 49, "/home/%s/.xchat2/otr-privkeys", username);
  snprintf(fingerpfile, 49, "/home/%s/.xchat2/otr-fingerprints", username);

  ctx = xchat_get_context(ph);
  if (ctx){
    if (nickname) free(nickname);
    nickname = strdup(ctx->me->nick);
  }

  *plugin_name = "X-Chat OTR Messaging";
  *plugin_desc = "Off the Record messaging plugin for secure and "
    "authentic communication";
  *plugin_version = "$Rev$";

  xchat_hook_server(ph, "PRIVMSG", XCHAT_PRI_NORM, privmsg_rcv_cb, NULL);
  xchat_hook_command(ph, "STARTOTR", XCHAT_PRI_NORM, startotr_cb,
      "Start an OTR conversation in current context", NULL);
  xchat_hook_command(ph, "", XCHAT_PRI_NORM, privmsg_send_cb, NULL, NULL);

  OTRL_INIT;
  userstate = otrl_userstate_create();

  if(stat(privkeyfile, &statbuf) && errno == ENOENT){
    otrl_privkey_generate(userstate, privkeyfile, username, "irc");
    chmod(privkeyfile, S_IRUSR|S_IWUSR);
  }
  if(stat(fingerpfile, &statbuf) && errno == ENOENT){
    otrl_privkey_write_fingerprints(userstate, fingerpfile);
    chmod(fingerpfile, S_IRUSR|S_IWUSR);
  }

  otrl_privkey_read(userstate, privkeyfile);
  otrl_privkey_read_fingerprints(userstate, fingerpfile, NULL, NULL);

  ui_cbs.policy = ui_policy;
  ui_cbs.create_privkey = ui_create_privkey;
  ui_cbs.is_logged_in = ui_is_logged_in;
  ui_cbs.inject_message = ui_inject_message;
  ui_cbs.notify = ui_notify;
  ui_cbs.display_otr_message = ui_display_otr_message;
  ui_cbs.update_context_list = NULL;
  ui_cbs.protocol_name = ui_protocol_name;
  ui_cbs.protocol_name_free = NULL;
  ui_cbs.new_fingerprint = ui_new_fingerprint;
  ui_cbs.write_fingerprints = NULL;
  ui_cbs.gone_secure = ui_gone_secure;
  ui_cbs.gone_insecure = ui_gone_insecure;
  ui_cbs.still_secure = ui_still_secure;
  ui_cbs.log_message = ui_log_message;
  ui_cbs.max_message_size = ui_max_message_size;
  ui_cbs.account_name = NULL;
  ui_cbs.account_name_free = NULL;

  xchat_print(ph, "OTR Messaging plugin loaded.\n");

  return 1;
}

__declspec(dllexport)
int xchat_plugin_deinit(){
  free(username);
  free(privkeyfile);
  free(fingerpfile);
  xchat_print(ph, "Off The Record plugin UNloaded.\n");
  return 1;
}
